#include "curve.h"

#include <math.h>
#include <SDL2/SDL_opengl.h>

#include "dpi.h"

#define CURVE_WIDTH 0.6f
#define MITER_LIMIT 5

static void get_orth(float x, float y, float *ox, float *oy) {
	*ox = -y;
	*oy = x;
}

static float get_len2(float x, float y) {
	return x * x + y * y;
}

static float get_len(float x, float y) {
	return sqrt(get_len2(x, y));
}

static void normalize(float *x, float *y) {
	float l = get_len(*x, *y);
	*x /= l;
	*y /= l;
}

static void set_length(float *x, float *y, float length) {
	float l = get_len(*x, *y);
	l = length / l;
	*x *= l;
	*y *= l;
}

static void get_bisector(float x0, float y0, float x1, float y1, float *bx, float *by) {
	float l0 = get_len(x0, y0);
	float l1 = get_len(x1, y1);
	*bx = x0 * l1 + x1 * l0;
	*by = y0 * l1 + y1 * l0;
	if (get_len2(*bx, *by) < 0.00000001f) {
		get_orth(x0, y0, bx, by);
		return;
	}
}

static void project(float width, /* bi */float target_x, float target_y, /* orth */float direction_x, float direction_y, float *proj_x, float *proj_y) {
	float dx = direction_x * target_x;
	float dy = direction_y * target_y;
	float dot = dx + dy;
	target_x *= width;
	target_y *= width;
	*proj_x = target_x / dot;
	*proj_y = target_y / dot;
}

static float curve_r, curve_g, curve_b, curve_a;

#define FAC 0.7f

static void do_points(curve_function func, float t_begin, float t_end, float t_step, int do_left, int do_right) {
	float previous_x;
	float previous_y;
	func(t_begin, &previous_x, &previous_y);
	t_begin += t_step;
	
	float current_x;
	float current_y;
	func(t_begin, &current_x, &current_y);
	

	float delta_x = current_x - previous_x;
	float delta_y = current_y - previous_y;

	float orth_x;
	float orth_y;
	get_orth(delta_x, delta_y, &orth_x, &orth_y);
	normalize(&orth_x, &orth_y);

	float real_width = dpi_x(CURVE_WIDTH);

	glColor4f(curve_r, curve_g, curve_b, curve_a);
	if(do_left) glVertex2f(previous_x + orth_x * real_width, previous_y + orth_y * real_width);
	glColor4f(curve_r * FAC, curve_g * FAC, curve_b * FAC, curve_a);
	if(do_right) glVertex2f(previous_x - orth_x * real_width, previous_y - orth_y * real_width);

	while (t_begin + t_step < t_end) {
		t_begin += t_step;

		float next_x, next_y;
		func(t_begin, &next_x, &next_y);

		float n_delta_x = current_x - next_x;
		float n_delta_y = current_y - next_y;

		float bi_x, bi_y;
		get_bisector(delta_x, delta_y, n_delta_x, n_delta_y, &bi_x, &bi_y);

		float proj_x, proj_y;
		project(real_width, bi_x, bi_y, orth_x, orth_y, &proj_x, &proj_y);
		
		if (get_len2(proj_x, proj_y) / (real_width * real_width) > MITER_LIMIT * MITER_LIMIT) {
			set_length(&proj_x, &proj_y, MITER_LIMIT * real_width);
		}

		glColor4f(curve_r, curve_g, curve_b, curve_a);
		if (do_left) glVertex2f(current_x + proj_x, current_y + proj_y);
		glColor4f(curve_r * FAC, curve_g * FAC, curve_b * FAC, curve_a);
		if (do_right) glVertex2f(current_x - proj_x, current_y - proj_y);
	
		delta_x = -n_delta_x;
		delta_y = -n_delta_y;
		get_orth(delta_x, delta_y, &orth_x, &orth_y);
		normalize(&orth_x, &orth_y);

		current_x = next_x;
		current_y = next_y;
		previous_x = current_x;
		previous_y = current_y;
	}

	orth_x *= real_width;
	orth_y *= real_width;

	func(t_begin, &current_x, &current_y);
	glColor4f(curve_r, curve_g, curve_b, curve_a);
	if (do_left) glVertex2f(current_x + orth_x, current_y + orth_y);
	glColor4f(curve_r * FAC, curve_g * FAC, curve_b * FAC, curve_a);
	if (do_right) glVertex2f(current_x - orth_x, current_y - orth_y);
}



void curve_color(float r, float g, float b, float a) {
	curve_r = r;
	curve_g = g;
	curve_b = b;
	curve_a = a;
}

void curve_draw(curve_function func, float t_begin, float t_end, float t_step) {
	
	glBegin(GL_QUAD_STRIP);
		do_points(func, t_begin, t_end, t_step, 1, 1);
	glEnd();
}